package ms.unicorn.dsl.eistar.reasoning.maxbelief;

import java.io.PrintStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import ms.unicorn.dsl.eistar.util.Probability;
import eistar.Entity;
import eistar.Goal;
import eistar.Model;
import eistar.Relation;
import eistar.RelationType;

public class MaxBeliefTable {
	
	class MaxBeliefEntry implements Comparable<MaxBeliefEntry> {
		private Goal goal;
		private Set<Goal> configuration;
		private int maxBelief, residualRisk;
		
		public MaxBeliefEntry(Goal goal, Set<Goal> configuration) {
			this.goal = goal;
			this.configuration = configuration;
		}
		
		@Override
		public boolean equals(Object obj) {
			if (obj instanceof MaxBeliefEntry) {
				MaxBeliefEntry e = (MaxBeliefEntry) obj;
				if (goal == null || !goal.equals(e.goal))
					return false;
				return (configuration.equals(e.configuration));
			}
			return super.equals(obj);
		}

		public void setGoal(Goal goal) {
			this.goal = goal;
		}

		public Goal getGoal() {
			return goal;
		}
		
		public Set<Goal> getConfiguration() {
			return configuration; 
		}

		public void setResidualRisk(int residualRisk) {
			this.residualRisk = residualRisk;
		}

		public int getResidualRisk() {
			return residualRisk;
		}

		public void setMaxBelief(int maxBelief) {
			this.maxBelief = maxBelief;
		}

		public int getMaxBelief() {
			return maxBelief;
		}
	
		@Override
		public String toString() {
			StringBuilder builder = new StringBuilder();
			builder.append(String.format("%s: ", goal != null ? goal.getName() : "<null>"));
			boolean first = true;
			for(Goal g: configuration) {
				if (!first)
					builder.append(',');
				builder.append(g.getName());
				first = false;
			}
			builder.append(String.format(" : mb=%d, rr=%d", maxBelief, residualRisk));
			return builder.toString();
		}
	
		public MaxBeliefEntry clone() {
			MaxBeliefEntry result = new MaxBeliefEntry(goal, configuration);
			result.maxBelief = maxBelief;
			result.residualRisk = residualRisk;
			return result;
		}
		
		@Override
		public int compareTo(MaxBeliefEntry o) {
			int res = goal.compareTo(o.goal);
			if (res == 0) 
				res = configuration.size() - o.configuration.size();
			if (res == 0)
				for(Goal g: configuration)
					if (!o.configuration.contains(g))
						return this.hashCode() - o.hashCode();
				
			return res;
		}
		
		public boolean isInclude(MaxBeliefEntry o) {
			boolean res = goal.compareTo(o.goal) == 0 &&
					configuration.size() >= o.configuration.size();
			if (res)
				for (Goal g: o.configuration)
					if (!configuration.contains(g))
						return false;
			return res;
		}
		
		public void update(MaxBeliefEntry t) {
			setMaxBelief(Math.max(maxBelief, t.maxBelief));
			setResidualRisk(Math.max(0, residualRisk + t.residualRisk - Probability.ONE));
		}
	}

	private List<MaxBeliefEntry> entries;

	public MaxBeliefTable() {
		entries = new ArrayList<MaxBeliefTable.MaxBeliefEntry>();
	}

	public List<MaxBeliefEntry> getEntries() {
		return entries;
	}
	
	public void generateFromModel(Model m) {
		clear();
		//ArrayList<MaxBeliefEntry> pendingList = new ArrayList<MaxBeliefTable.MaxBeliefEntry>();
		Map<Goal, List<MaxBeliefEntry>> entries = new HashMap<Goal, List<MaxBeliefEntry>>();
		
		// 1st round: construct the initial table which contain all relations
		for(Relation rel: m.getModelElement(Relation.class))
			switch(rel.getRelType()) {
			case DECOMPOSE:
			case DELEGATE:
				addRelationToTable(rel, entries);
			}
					
		// 2nd round: go through the table and expand the relation, loop until their is no expandable rule
		// In this round we use MaxBelief of each entry as a flag which could be one of the following
		//		0 		: the goal of this entry is a top goal
		//		MAX_INT : the goal of this entry is not a top goal
		boolean conti = false;
		do { 
			conti = false;
			for(List<MaxBeliefEntry> list: entries.values()) 
				for(int i = 0; i < list.size(); i++) {
					MaxBeliefEntry entry = list.get(i);
					if (entry.getMaxBelief() == 0) { // only process top goal
						for(Object g: entry.configuration.toArray()) { // walk though all goal in this configuration
							if (entries.containsKey(g)) {
								conti = true; // we might need another loop
								boolean first = true;
								List<MaxBeliefEntry> subrules = entries.get(g);
								Set<Goal> oldConfig = subrules.size() > 1 ? new TreeSet<Goal>(entry.configuration) : entry.configuration;
								for (MaxBeliefEntry subentry : subrules) {
									subentry.setMaxBelief(Integer.MAX_VALUE); // mark this entry as non top goal
									MaxBeliefEntry newEntry = first ? entry : (new MaxBeliefEntry(entry.goal, oldConfig));
									if (!first) 
										list.add(newEntry);
									newEntry.configuration.remove(g);
									for(Goal sg: subentry.configuration)
										newEntry.configuration.add(sg);
									first = false;
								}
							}
						}
					}
				}
		} while (conti);
		// 3rd round: remove all non-top goal entries
		for (Object goal : entries.keySet().toArray()) {
			List<MaxBeliefEntry> list = entries.get(goal);
			for (MaxBeliefEntry entry: list)
				if (entry.getMaxBelief() != Integer.MAX_VALUE)
					this.entries.add(entry);
			/*if (list.size() == 0 || list.get(0).getMaxBelief() == Integer.MAX_VALUE)
				entries.remove(goal);*/
		}
	}

	private void addRelationToTable(Relation rel, Map<Goal, List<MaxBeliefEntry>> entries) {
		Goal pGoal = (Goal) rel.getSource();
		Set<Goal> cGoals = new TreeSet<Goal>();
		for (Entity target: rel.getTarget())
			cGoals.add((Goal) target);
		
		List<MaxBeliefEntry> list = entries.get(pGoal);
		if (list == null)
			entries.put(pGoal, list = new ArrayList<MaxBeliefTable.MaxBeliefEntry>());
		list.add(new MaxBeliefEntry(pGoal, cGoals));
		
	}

	public void printTableContent(PrintStream out) {
		//for(List<MaxBeliefEntry> list: entries.values())
		for(MaxBeliefEntry entry: entries)
			out.println(entry.toString());
	}

	
	public void clear() {
		entries.clear();	
	}

	public void setMaxBelief(int value) {
		for(MaxBeliefEntry entry: entries)
			entry.setMaxBelief(value);
	}
	
	public void setResidualRisk(int value) {
		for(MaxBeliefEntry entry: entries)
			entry.setResidualRisk(value);
	}

	private MaxBeliefEntry findEntry(MaxBeliefEntry entry) {
		for(MaxBeliefEntry e: entries)
			if (e.compareTo(entry) == 0)
				return e;
		return null;
	}
	
	public MaxBeliefTable merge(MaxBeliefTable btable) {
		for(MaxBeliefEntry rEntry: btable.getEntries()) {
			MaxBeliefEntry lEntry = findEntry(rEntry);
			MaxBeliefEntry newEntry = lEntry != null ? lEntry : rEntry.clone();
			
			// first add the rEntry to the table if it does not exist
			if (lEntry == null) 
				entries.add(newEntry);
			else 
				lEntry.update(rEntry);

			// scan the list and update entries that subsumes/is subsumed by newEntry 
			for (MaxBeliefEntry e:entries)
				if (e != newEntry)
					if (newEntry.isInclude(e))
						newEntry.update(e); 
					else if (e.isInclude(newEntry))
						e.update(rEntry); // rEntry instead of newEntry to avoid cumulative update.
				
			
			/*if (lEntry != null) {
				lEntry.setMaxBelief(Math.max(lEntry.maxBelief, rEntry.maxBelief));
				lEntry.setResidualRisk(Math.max(lEntry.residualRisk + rEntry.residualRisk - Probability.ONE, 0));
			}
			else {
				MaxBeliefEntry entry = rEntry.clone();
				for (MaxBeliefEntry e: entries)
					if (entry.isInclude(e)) {
						entry.setMaxBelief(Math.max(entry.maxBelief, e.maxBelief));
						entry.setResidualRisk(Math.max(0, entry.residualRisk + e.residualRisk - Probability.ONE));
					}
				entries.add(entry);
			}*/
		}
		return this;
	}
}
